`timescale 1ns / 1ps

module ut_reduce;

    // Parameters
    localparam int unsigned WIDTH = 8;
    localparam int unsigned N = 4;
    localparam int unsigned NUM_TESTS = 100;

    // Clock and reset
    logic               clk;
    logic               rst_n;

    // DUT signals
    logic [WIDTH*N-1:0] in_data;
    logic               in_valid;
    logic               in_ready;
    logic [  WIDTH-1:0] out_data;
    logic               out_valid;
    logic               out_ready;

    // Test queues
    logic [WIDTH*N-1:0] input_queue  [$];
    logic [  WIDTH-1:0] output_queue [$];
    logic [  WIDTH-1:0] golden_queue [$];

    // Test control
    int                 input_count;
    int                 output_count;
    int                 error_count;

    // Clock generation
    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 100MHz clock
    end

    // DUT instantiation
    reduce #(
        .WIDTH(WIDTH),
        .N    (N)
    ) dut (
        .clk      (clk),
        .rst_n    (rst_n),
        .in_data  (in_data),
        .in_valid (in_valid),
        .in_ready (in_ready),
        .out_data (out_data),
        .out_valid(out_valid),
        .out_ready(out_ready)
    );

    // Input stimulus
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            in_valid <= 1'b0;
            in_data  <= '0;
        end else begin
            if (in_valid && in_ready) begin
                input_queue.pop_front();
                input_count++;
            end

            if (input_queue.size() > 0) begin
                in_valid <= 1'b1;
                in_data  <= input_queue[0];
            end else begin
                in_valid <= 1'b0;
                in_data  <= '0;
            end
        end
    end

    // Random out_ready control
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            out_ready <= 1'b0;
        end else begin
            out_ready <= 1'b1;
        end
    end

    // Collect outputs
    always @(posedge clk) begin
        if (rst_n && out_valid && out_ready) begin
            output_queue.push_back(out_data);
            output_count++;
        end
    end

    // Generate test data and golden reference
    task generate_test_data();
        logic [WIDTH*N-1:0] wide_data;

        $display("\n========================================");
        $display("Generating Test Data");
        $display("========================================");

        for (int i = 0; i < NUM_TESTS; i++) begin
            // Generate random wide data
            wide_data = $urandom();
            input_queue.push_back(wide_data);

            // Generate golden outputs (N narrow outputs per wide input)
            for (int j = 0; j < N; j++) begin
                logic [WIDTH-1:0] narrow_data;
                narrow_data = wide_data[j*WIDTH+:WIDTH];
                golden_queue.push_back(narrow_data);
            end

            if (i < 5) begin
                $display("Input[%0d] = 0x%0h", i, wide_data);
                for (int j = 0; j < N; j++) begin
                    $display("  -> Output[%0d] = 0x%02h", i * N + j, wide_data[j*WIDTH+:WIDTH]);
                end
            end
        end

        $display("Generated %0d input words, %0d output bytes", NUM_TESTS, NUM_TESTS * N);
    endtask

    initial begin
        // Initialize
        rst_n        = 0;
        input_count  = 0;
        output_count = 0;
        error_count  = 0;

        // Reset
        repeat (10) @(posedge clk);
        rst_n = 1;
        $display("Reset released at time %0t", $time);

        // Generate test data
        generate_test_data();

        // Wait a bit
        repeat (5) @(posedge clk);

        // Wait for all inputs to be sent
        wait (input_queue.size() == 0);
        @(posedge clk);
        $display("\nAll inputs sent at time %0t (count=%0d)", $time, input_count);

        // Wait for all outputs
        repeat (1000) @(posedge clk);

        // Verify outputs
        $display("\n========================================");
        $display("Starting Output Verification");
        $display("========================================");

        error_count = 0;
        if (output_queue.size() != golden_queue.size()) begin
            $display("ERROR: Size mismatch! output_queue=%0d, golden_queue=%0d", output_queue.size(),
                     golden_queue.size());
        end

        for (int i = 0; i < golden_queue.size(); i++) begin
            logic [WIDTH-1:0] expected, actual;

            expected = golden_queue[i];
            actual   = (i < output_queue.size()) ? output_queue[i] : 'x;

            if (actual !== expected) begin
                $display("ERROR at output[%0d]: expected=0x%02h, actual=0x%02h", i, expected, actual);
                error_count++;
            end else if (i < 20 || error_count > 0) begin
                $display("PASS output[%0d]: value=0x%02h", i, actual);
            end
        end

        // Report results
        $display("\n========================================");
        $display("Test Summary");
        $display("========================================");
        $display("Total inputs sent: %0d", input_count);
        $display("Total outputs received: %0d", output_count);
        $display("Expected outputs: %0d", golden_queue.size());
        $display("Errors found: %0d", error_count);

        if (error_count == 0 && output_queue.size() == golden_queue.size()) begin
            $display("*** TEST PASSED ***");
        end else begin
            $display("*** TEST FAILED ***");
        end
        $display("========================================\n");

        $finish;
    end

    // Waveform dump for Verdi
    initial begin
        $fsdbDumpfile("ut_reduce.fsdb");
        $fsdbDumpvars(0, ut_reduce);
        $fsdbDumpvars("+all");
    end

endmodule
